﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MacomberMapSystem.Common.CIM;
using MacomberMapSystem.Common.Database;
using System.Xml;
using MacomberMapSystem.Common.User_Interfaces.One_Lines;
using System.Drawing.Drawing2D;
using System.Drawing;
using MacomberMapSystem.Common.Integration;
using MacomberMapSystem.Common.Serialization;
using MacomberMapSystem.Common.Internals;

namespace MacomberMapSystem.Common.Exports
{
    /// <summary>
    /// This class holds all the needed information to export a one-line, creating and storing critical information so that it doesn't need to be recreated multiple times.
    /// </summary>
    public class MM_Oneline_Export
    {
        #region Variable declarations
        /// <summary>The base element for the one-line (e.g., substation, b2b trace, company)</summary>
        public CIM_Element BaseElement;

        /// <summary>The one-line for processing</summary>
        public MM_Database_OneLine OneLine;

        /// <summary>The model associated with the export</summary>
        public MM_Database_Model Model;

        /// <summary>Our lookup table by TEID</summary>
        public Dictionary<UInt64, CIM_Element> ElementsByTEID = new Dictionary<ulong, CIM_Element>();

        /// <summary>Our lookup table by rdf:ID</summary>
        public Dictionary<CIM_RdfID, CIM_Element> ElementsByRdfID = new Dictionary<CIM_RdfID, CIM_Element>();

        /// <summary>Our XML elements associated with CIM elements</summary>
        public Dictionary<CIM_Element, XmlElement> MappedElements = new Dictionary<CIM_Element, XmlElement>();

        /// <summary>Our CIM elements associated with XML elements</summary>
        public Dictionary<XmlElement, CIM_Element> ElementMapping = new Dictionary<XmlElement, CIM_Element>();

        /// <summary>Our element boundaries associated with XML elements</summary>
        public Dictionary<XmlElement, Rectangle> ElementBounds = new Dictionary<XmlElement, Rectangle>();
        
        /// <summary>Our paths associated with our connectivity nodes</summary>
        public Dictionary<GraphicsPath, String> NodePaths = new Dictionary<GraphicsPath, String>();

        /// <summary>Our KV levels associated with elements</summary>
        public Dictionary<CIM_Element, String> KVLevels = new Dictionary<CIM_Element, String>();

        /// <summary>Our Value to XML element linkages</summary>
        public Dictionary<MM_OneLine_Value, XmlElement> OutValues = new Dictionary<MM_OneLine_Value, XmlElement>();

        /// <summary>Our collection of RAS gates</summary>
        public List<CIM_Element> RASGates = new List<CIM_Element>();
        #endregion

        /// <summary>
        /// Initialize a new model export
        /// </summary>
        /// <param name="OneLine"></param>
        /// <param name="Model"></param>
        public MM_Oneline_Export(MM_Database_OneLine OneLine, MM_Database_Model Model)
        {
            this.OneLine = OneLine;
            this.Model = Model;
            try
            {
                DetermineBaseElement();
                BuildElementList();
                CheckForRASElements();
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine("Error in building export for " + OneLine.rdfID + " in " + Model.Name + ": " + MM_System_Interface.ConvertError(ex));
            }
        }

        /// <summary>Build our collection of RAS gates</summary>
        public void CheckForRASElements()
        {
            CIM_Element OutputGate;
            foreach (CIM_Element Elem in MappedElements.Keys)
            foreach (CIM_Element SPS in Elem.SPSorRAPs)
                foreach (CIM_Element Gate in SPS.Parse("TriggerCondition<Gate", null))
                    try
                    {
                        String ShowTelem = Gate.ParseSingle("TriggerCondition>RemedialActionScheme", Model)["etx:RemedialActionScheme.showTelemOnDisplay"];
                        if (!RASGates.Contains(Gate) && (String.IsNullOrEmpty(ShowTelem) || XmlConvert.ToBoolean(ShowTelem)))
                            if (Gate.ParseSingle("HasAMeasurement", null) != null)
                                RASGates.Add(Gate);
                            else if ((OutputGate = Gate.ParseSingle("TriggerCondition>Gate", null)) != null && !RASGates.Contains(OutputGate) && Gate.ParseSingle("TriggerCondition<Gate<HasAMeasurement", null) == null)
                                RASGates.Add(OutputGate);
                    }
                    catch (Exception ex)
                    { }
        }

        /// <summary>
        /// Build our list of elements
        /// </summary>
        private void BuildElementList()
        {
            //Pull in our information that we'll need from the model first.
            List<String> ExcludeTypes = new List<string>(5);
            ExcludeTypes.Add("BusbarSection");
            ExcludeTypes.Add("Bay");
            ExcludeTypes.Add("ConnectivityNodeGroup");
            ExcludeTypes.Add("SynchrocheckRelay");
            ExcludeTypes.Add("FrequencyRelay");
            ExcludeTypes.Add("StationSupply");
            ExcludeTypes.Add("VoltageRelay");
            ExcludeTypes.Add("GroundSwitch");
            ExcludeTypes.Add("VoltageMonitored");
            this.ElementBounds.Clear();
            this.ElementMapping.Clear();
            this.ElementsByRdfID.Clear();
            this.ElementsByTEID.Clear();
            this.KVLevels.Clear();
            this.MappedElements.Clear();
            foreach (GraphicsPath gp in this.NodePaths.Keys)
                gp.Dispose();
            this.NodePaths.Clear();

            this.OutValues.Clear();

            //Build our internal representation of topology
            foreach (CIM_Element cElem in BaseElement.Elements)
            {
                UInt64 FoundTEID;
                if (!ExcludeTypes.Contains(cElem.ElemType))
                {
                    ElementsByRdfID.Add(cElem.rdfID, cElem);
                    if (UInt64.TryParse(cElem.TEID, out FoundTEID))
                        ElementsByTEID.Add(FoundTEID, cElem);
                }
            }

            //Pull in all of our element information
            foreach (String ValToCheck in "/One_Line/Elements/*,/One_Line/Nodes/*,/One_Line/Nodes/Node/*,//PricingVector".Split(','))
                //foreach (String ValToCheck in "/One_Line/Elements/*,/One_Line/Elements/Transformer/Winding,/One_Line/Nodes/*,/One_Line/Nodes/Node/*,//PricingVector".Split(','))            
                foreach (XmlElement xElem in OneLine.Document.SelectNodes(ValToCheck))
                    try
                    {
                        //First, try and locate our element
                        CIM_Element FoundElem = null;
                        if (xElem.HasAttribute("rdfID"))
                            ElementsByRdfID.TryGetValue(new CIM_RdfID(xElem.Attributes["rdfID"].Value), out FoundElem);
                        if (FoundElem == null)
                            if (xElem.HasAttribute("BaseElement.TEID") && !String.IsNullOrEmpty(xElem.Attributes["BaseElement.TEID"].Value))
                                ElementsByTEID.TryGetValue(Convert.ToUInt64(xElem.Attributes["BaseElement.TEID"].Value), out FoundElem);
                            else if (xElem.HasAttribute("TEID"))
                                ElementsByTEID.TryGetValue(Convert.ToUInt64(xElem.Attributes["TEID"].Value), out FoundElem);

                        if (FoundElem != null)
                        {

                            //Search for descriptors and secondary descriptors, and pull in their coordinates
                            XmlElement xDesc = xElem["Descriptor"];
                            XmlElement xSDesc = xElem["SecondaryDescriptor"];
                            String Voltage = null;
                            if (FoundElem.VoltageLevel != null)
                                Voltage = Data_Manager.LocateKVLevel(FoundElem.VoltageLevel.Name).Name.ToUpper();
                            if (xDesc == null)
                                xDesc = xElem.SelectSingleNode("*/PokePoint/Descriptor") as XmlElement;
                            if (xDesc != null)
                                ElementBounds.Add(xDesc, MM_Serializable<Rectangle>.ConvertObject(xDesc.Attributes["Bounds"].Value, null));
                            if (xSDesc != null)
                                ElementBounds.Add(xSDesc, MM_Serializable<Rectangle>.ConvertObject(xSDesc.Attributes["Bounds"].Value, null));

                            //If we're a node, build our graphics path to the node, and add our poke points                        
                            if (xElem.Name == "Descriptor")
                                foreach (MM_OneLine_Value Val in MM_OneLine_Element.BuildExportPointsWithLocations(xElem, MM_OneLine_Element.BuildExportPoints(FoundElem, BaseElement, xElem.Name == "SecondaryDescriptor", xDesc), ElementBounds[MappedElements[FoundElem]], ElementBounds[xElem]))
                                    OutValues.Add(Val, xElem.ParentNode as XmlElement);
                            else if (xElem.ParentNode.Name != "Node")
                            {
                                if (!MappedElements.ContainsKey(FoundElem))
                                    MappedElements.Add(FoundElem, xElem);
                                ElementMapping.Add(xElem, FoundElem);
                                if (Voltage != null && !KVLevels.ContainsKey(FoundElem))
                                    KVLevels.Add(FoundElem, Voltage);
                                ElementBounds.Add(xElem, MM_Serializable<Rectangle>.ConvertObject(xElem.Attributes["Bounds"].Value, null));
                                if (xDesc != null)
                                    foreach (MM_OneLine_Value Val in MM_OneLine_Element.BuildExportPointsWithLocations(MappedElements[FoundElem], MM_OneLine_Element.BuildExportPoints(FoundElem, BaseElement, xDesc.Name == "SecondaryDescriptor", xDesc), ElementBounds[MappedElements[FoundElem]], ElementBounds[xDesc]))
                                        OutValues.Add(Val, xDesc.ParentNode as XmlElement);

                            }
                            else if (xElem.Name == "SecondaryDescriptor")
                                ElementMapping.Add(xElem, FoundElem);
                            else
                                try
                                {
                                    //Find our node
                                    CIM_Element NodeElem = null;
                                    if ((xElem.ParentNode as XmlElement).HasAttribute("rdfID"))
                                        ElementsByRdfID.TryGetValue(new CIM_RdfID(xElem.ParentNode.Attributes["rdfID"].Value), out NodeElem);
                                    if (NodeElem == null && (xElem.ParentNode as XmlElement).HasAttribute("BaseElement.TEID"))
                                        ElementsByTEID.TryGetValue(Convert.ToUInt64(xElem.ParentNode.Attributes["BaseElement.TEID"].Value), out NodeElem);
                                    if (NodeElem == null)
                                    {
                                        NodeElem = Model.FromRdfID(new CIM_RdfID(xElem.ParentNode.Attributes["rdfID"].Value));
                                        if (NodeElem != null)
                                          KVLevels.Add(NodeElem, Data_Manager.LocateKVLevel(NodeElem.VoltageLevel).Name);
                                    }
                                    //Build our path to our element
                                    if (NodeElem != null)
                                        NodePaths.Add(MM_OneLine_Element.BuildNodePath(xElem.ParentNode as XmlElement, xElem), KVLevels[NodeElem]);

                                    //Add all poke points
                                    foreach (XmlElement xPokePoint in xElem.SelectNodes("PokePoint"))
                                        ElementBounds.Add(xPokePoint, MM_Serializable<Rectangle>.ConvertObject(xPokePoint.Attributes["Bounds"].Value, null));
                                }
                                catch (Exception ex2)
                                {
                                    Console.Error.WriteLine("Error in building export for " + OneLine.rdfID + " in " + Model.Name + ": " + MM_System_Interface.ConvertError(ex2));
                                }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.Error.WriteLine("Error in building export for " + OneLine.rdfID + " in " + Model.Name + ": " + MM_System_Interface.ConvertError(ex));
                    }

        }

        /// <summary>
        /// Determine our base element
        /// </summary>
        private void DetermineBaseElement()
        {
            //Determine the one-line's base element
            if (OneLine.OneLineType == MM_Database_OneLine.OneLineTypeEnum.Substation)
                BaseElement = Model.FromRdfID(OneLine.rdfID);
            else if (Model.Traces.Count > 0)
                BaseElement = Model.Traces[OneLine.Document.DocumentElement.Attributes["BaseElement.Name"].Value];

            if (BaseElement != null)
                return;

            //Determine the one-line's base element
            if (BaseElement == null && OneLine.Document.DocumentElement.HasAttribute("rdfID") && !string.IsNullOrEmpty(OneLine.Document.DocumentElement.Attributes["rdfID"].Value))
                BaseElement = Model.FromRdfID(OneLine.Document.DocumentElement.Attributes["rdfID"].Value);
            else
            {
                //If we don't have an rdf:ID, let's add it in
                BaseElement = Model.FromTEID(Convert.ToUInt64(OneLine.Document.DocumentElement.Attributes["BaseElement.TEID"].Value));
                OneLine.Document.DocumentElement.Attributes.Append(OneLine.Document.CreateAttribute("rdfID")).Value = BaseElement.rdfID.ToString();
            }
                
        }
    }
}
